Ghodratollah Aalipour (ga5481@rit.edu)

Description

YouTube maintains a list of the top trending videos. To determine the year’s top-trending videos, YouTube uses a combination of factors including measuring users interactions (number of views, shares, comments and likes). Our dataset for this project is derived from [1].

Our dataset is a daily record of the top trending YouTube videos and includes several months (and counting) of data on daily trending YouTube videos. The regions that this dataset covers are the US, GB, DE, CA, and FR regions (USA, Great Britain, Germany, Canada, and France, respectively), with up to 200 listed trending videos per day. Each region’s data is in a separate file. We focus on the file for the United States. Kaggle collected this data through YouTube API.

The data includes several features such as the video title, trending_date, channel title, publish time, tags, views, likes and dislikes, description, and comment count with 23363 observations. The variables likes, dislikes, comment counts are the only continuous variables. There exist text data and time-series data as well.

Goal

In this project we aim to relate the number of views to other numeric variables through a linear regression model and perform statistically analysis techniques that we learned in the course. This includes identifying the factors that have most influence on the model. We start with loading the data and preprocess it to handle missing values etc. First, we include all the packages the we need for this project.

Required Libraries

library(ggplot2)
library(dplyr)
library(corrplot)
library(lubridate)

As we pointed out, the data is given in [1]. But we download it and save it to the disk.

Loading the Data

us_videos <- read.csv("USvideos.csv")
us_videos

Data Preprocessing

We start with some initial data processing. We do not need all features of the data. To occupy less memory, we drop unnecessary columns.

Dropping Unfavorable Columns

We drop the following columns: “video_id”, “trending_date”, “views”, “likes”, “dislikes”, “comment_count”, “category_id”, “publish_time”,“channel_title”, and “title”.

favor_cols <- c("video_id", "trending_date",  "views", "likes", "dislikes", "comment_count", 
                "category_id", "publish_time","channel_title", "title")
us_videos <- us_videos[ , favor_cols] 
us_videos$category_id <- as.factor(us_videos$category_id)
us_videos

Any Missing value?

As the code block below shows, there is no missing value in our data.

sum(is.na(us_videos))
[1] 0

Understanding Our Data Better

Question: Do we have exactly one observation per video?

The videos are determined by their ID’s. So, we count the number of different ID’s as a factor variable. If this number matchs with the total number of observations, then we have exactly one instance per video.

length(levels(as.factor(us_videos$video_id)))
[1] 4712

Hence, we only have \(4,712\) videos but the number of observations is \(23,362\). So we have redundant information for a single video. For instance, for a video whose ID is “2kyS6SvSYSE”, we take a look to see what information can be captured form this video.

us_videos[us_videos$video_id == "2kyS6SvSYSE",]

As it can be seen from this query, for this video id we have \(7\) observations. After a careful look at the data, we realize that each is per trending day. This video has been in trending for seven days from November 14, 2011 to November 20, 2011. It was published in November 13. Thus, each video has appeared in the observations as many days as it has been trending.

We can use the group_by method of “dplyer” library of R to group the data observations by their video_id.

arrange(us_videos, video_id, desc(trending_date))

How is the Data Collected?

Are the counts for views, likes, dislikes and comment_count computed cumulatively or are they resenting the values for each day. Unfortunately, I did not find any information in the data source regarding this issue. I assume that they are calculated cumulatively as per watch a video in the YouTube, the number of views increases by one.

Removing History for Trend

We extract actual numeric values by aggregating over the history for each video so that we have exactly one data instance for each video. We call the new dataset by “num_feature”.

num_feature<- us_videos %>% group_by(video_id) %>%   
  summarize(
                        views = sum(views), likes = max(likes), 
                        dislikes = max(dislikes), comment_count = max(comment_count)
                        ) 
num_feature

Boxplot for Views Based on the Category ID

We plot the mean, median and variance of views per category_id. Unfortunately, there are extreme outliers and they do not allow see the details well in the plot.

us_videos %>% group_by(category_id) %>%   
  summarize(mean_views = mean(views), sd_views = sd(views)) 
categ_data <- us_videos %>% group_by(video_id, category_id) %>%   
  summarize(
                        views = sum(views), likes = max(likes), 
                        dislikes = max(dislikes), comment_count = max(comment_count)
                        ) 
p <- ggplot(categ_data, aes(x=category_id, y=views, color = category_id) )+ geom_boxplot(outlier.colour="black", outlier.shape=16,
             outlier.size=2, notch=FALSE) + coord_flip()
p

Modelling The Data

We start with the full model, which include all variables.

view_full <- lm(views ~ likes + dislikes + comment_count, data = num_feature)
summary(view_full)

Call:
lm(formula = views ~ likes + dislikes + comment_count, data = num_feature)

Residuals:
      Min        1Q    Median        3Q       Max 
-1.56e+08 -1.60e+06 -1.19e+06 -2.20e+05  4.60e+08 

Coefficients:
               Estimate Std. Error t value Pr(>|t|)    
(Intercept)    1.25e+06   2.28e+05     5.5  4.1e-08 ***
likes          1.61e+02   2.37e+00    68.2  < 2e-16 ***
dislikes       3.79e+02   1.18e+01    32.1  < 2e-16 ***
comment_count -4.50e+02   1.75e+01   -25.7  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1.5e+07 on 4708 degrees of freedom
Multiple R-squared:  0.649, Adjusted R-squared:  0.649 
F-statistic: 2.9e+03 on 3 and 4708 DF,  p-value: <2e-16

The p-value of the F-statistic is less than \(2.2*10^{-16}\) which means that the regression is significant, i.e. the coefficients of at least one the predictors “likes”, “dislikes”, “comment_count” is non-zero.

The regression model determined by the least square method on this dataset is

\[{\bf views} = 1255000 + 161.5 * ({\bf likes}) + 378.9 * ({\bf dislikes}) - 450.4 * ({\bf comment\_count})\] The adj-\(R^2\) is \(0.64\) which is not that high. This value could be due to multicollinearity among the predictors. We check for collinearity among the predictors.

Correlations Among the New Variables

cor_matrix <- cor(num_feature[, c("views", "likes", "dislikes", "comment_count")])
corrplot(cor_matrix, method = "number") #order = "hclust"

Multi-Collinearity

As the correlation matrix shows, there is high collinearity between some variables. For instance, dislikes and comment count have high correlations. This makes sense, because usually when people are angry about a product, they would write a review. Similarly, views and likes have high correlation as well. So likes seems to be a good predictor for the number of views. Moreover, likes and the comment counts have remarkable correlation. Thus, if one of the predictors likes or comment count is available in the model, the other one might be considered for exclusion from the model. We will examine these dependencies later when we are modelling.

Variance Inflation Factors - VIF Score

We calculate the Variance Inflation Factor (VIF) among the predictor variables.

car::vif(view_full)
        likes      dislikes comment_count 
          2.4           3.7           6.1 

The VIF for “comment_count” is begiier that \(5\). Since \(5 \leq VIF \leq 10\) are considered significant, the predictor “comment_count” would poorly estimate the regression coefficient.

Condition Number

kappa(num_feature[,c("likes", "dislikes", "comment_count")])
[1] 15

Dropping the comment_count from the Full Model

We start reducing our model by dropping the predictor whose VIF is high. Thus, we drop the “comment_count” from our full model. Then, we evaluate the reduced model for its VIF.

view_like_dislike <- lm(views ~ likes + dislikes, data = num_feature)
car::vif(view_like_dislike)
   likes dislikes 
     1.3      1.3 

VIF For the New Reduced Model

As it is clear from the new VIF’s for the new model, each of new “likes” and “dislikes” have a good VIF. So, we prefer the reduced model over the full model.

How About the adj-\(R^2\) for the reduced Model?

Though, we do not see any significant VIF, we should compare the performance of the reduced model with the full model in terms of the adj-\(R^2\).

summary(view_like_dislike)

Call:
lm(formula = views ~ likes + dislikes, data = num_feature)

Residuals:
      Min        1Q    Median        3Q       Max 
-2.35e+08 -1.59e+06 -1.23e+06 -3.28e+05  4.60e+08 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) 1.27e+06   2.44e+05    5.22  1.9e-07 ***
likes       1.20e+02   1.84e+00   64.98  < 2e-16 ***
dislikes    1.32e+02   7.31e+00   18.08  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1.6e+07 on 4709 degrees of freedom
Multiple R-squared:   0.6,  Adjusted R-squared:   0.6 
F-statistic: 3.53e+03 on 2 and 4709 DF,  p-value: <2e-16

The regression model induced by these predictors is

\[views = 1272000 + 119.9 * (likes) + 132.1* (dislikes)\] The adj-\(R^2\) for the new model is \(0.59\) but for the full model is \(0.64\). So, we are missing some amount of accuracy. If there was no loss on the accuracy, then the reduced model is preferred over the full model. But with this loss, we may need to check other model candidates by reducing the model even further.

Regression Relating Views to Likes

view_like <- lm(views ~ likes, data = num_feature)
summary(view_like)

Call:
lm(formula = views ~ likes, data = num_feature)

Residuals:
      Min        1Q    Median        3Q       Max 
-2.72e+08 -1.44e+06 -9.91e+05 -1.88e+05  4.60e+08 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) 1.02e+06   2.52e+05    4.04  5.5e-05 ***
likes       1.35e+02   1.70e+00   79.35  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1.7e+07 on 4710 degrees of freedom
Multiple R-squared:  0.572, Adjusted R-squared:  0.572 
F-statistic: 6.3e+03 on 1 and 4710 DF,  p-value: <2e-16

Thus, the linear regression model between “views” and “likes” is determined by \(views = 1016000 + 135*(likes)\). As the p-value for the F-statistic show, the regression is significant. The adj-\(R^2\) for this third model is \(0.57\) which is lower than the adj-\(R^2\) for the second model and the first model.

Confidence and Prediction Intervals

We take a look at the confidence intervals and prediction intervals.

temp_predic <- predict(view_like, interval="prediction")
predictions on current data refer to _future_ responses
new_df <- cbind(num_feature, temp_predic)
ggplot(new_df, aes(likes, views))+
geom_point() +
geom_line(aes(y=lwr), color = "red", linetype = "dashed")+
geom_line(aes(y=upr), color = "red", linetype = "dashed")+
geom_smooth(method=lm, se=TRUE)

What Model to Select?

We summarize our models in the table below:

Three Linear Regression Models

Three Linear Regression Models

As it is clear from the table, by reducing the models we miss some amount of accuracy. From the first model to the second model, we lose about \(4\%\) of accuracy. But the tradeoff is that we won’t have the collinearity in this model. The last model has only one predictor and has less accuracy in terms of adj-\(R^2\). So the best model could be the second model.

Scatter Plot for Data

Views vs Likes

ggplot(num_feature, aes(x = likes, y = views)) + geom_point()

Views vs Dislikes

ggplot(num_feature, aes(x = dislikes, y = views)) + geom_point()

Views vs Comment Count

ggplot(num_feature, aes(y = views, x = comment_count)) + geom_point()

Building Linear Models Relating Other Predictors

As we realized through the correlation matrix, there seems to be linear relation between views and likes and another linear relation between dislikes and comment_count. We investigate each of these single variable linear relations through applying least square linear regression models and check for the significance of regression.

Regression Relating Comment Count to Dislikes

dislike_comment <- lm(comment_count ~ dislikes, data = num_feature)
summary(dislike_comment)

Call:
lm(formula = comment_count ~ dislikes, data = num_feature)

Residuals:
    Min      1Q  Median      3Q     Max 
-347616   -3089   -2645   -1204  508732 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) 3.20e+03   2.51e+02    12.8   <2e-16 ***
dislikes    7.13e-01   6.95e-03   102.6   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 17200 on 4710 degrees of freedom
Multiple R-squared:  0.691, Adjusted R-squared:  0.691 
F-statistic: 1.05e+04 on 1 and 4710 DF,  p-value: <2e-16

Thus, our linear regression model between “comment_count” and “dislikes” is determined by \(comment\_count = 3199 + 0.713(dislikes)\). As the p-value for the F-statistic is too low, the regression is significant.

temp_predic <- predict(dislike_comment, interval="prediction")
predictions on current data refer to _future_ responses
new_df <- cbind(num_feature, temp_predic)
ggplot(new_df, aes(x = dislikes, y = comment_count))+
geom_point() +
geom_line(aes(y=lwr), color = "red", linetype = "dashed")+
geom_line(aes(y=upr), color = "red", linetype = "dashed")+
geom_smooth(method=lm, se=TRUE)

anova(dislike_comment)
Analysis of Variance Table

Response: comment_count
            Df   Sum Sq  Mean Sq F value Pr(>F)    
dislikes     1 3.11e+12 3.11e+12   10527 <2e-16 ***
Residuals 4710 1.39e+12 2.95e+08                   
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Catching Influential Data Points

The plots above show that there are some extreme outliners in our dataset. The influential data points adversely impact on our regression models and can favor some regression coefficients. To address this problem, we should find such point. We start with the leverage points:

Leverage Points

The following indices show that the corresponding data points are leverage points.

p = 2
n = 4712
inflc = influence(view_full)
inflc$hat[inflc$hat>2*p/n]
     18      50      77      79      84      96     102     156     287     315     316     323     355     388     463 
0.00236 0.00160 0.05615 0.00271 0.00449 0.00242 0.00209 0.00214 0.00112 0.01263 0.00121 0.00093 0.04849 0.00376 0.00134 
    489     512     531     539     635     645     664     730     732     736     757     760     771     790     794 
0.00105 0.00334 0.00331 0.00091 0.00235 0.00385 0.09073 0.00176 0.00100 0.01537 0.00722 0.00343 0.00243 0.00100 0.03680 
    852     864     872     877     910     917     932     967    1046    1091    1103    1123    1144    1152    1153 
0.00195 0.00129 0.00323 0.00836 0.00109 0.00096 0.00669 0.00721 0.00385 0.00498 0.00124 0.00097 0.00085 0.00093 0.00194 
   1211    1242    1316    1400    1408    1416    1502    1521    1529    1536    1559    1566    1574    1664    1667 
0.00241 0.00954 0.00527 0.04476 0.01235 0.00138 0.00091 0.00085 0.00487 0.00347 0.01777 0.00093 0.00249 0.00175 0.01663 
   1720    1731    1732    1742    1813    1820    1837    1918    1921    1944    1967    2028    2196    2215    2232 
0.00176 0.00188 0.61457 0.00142 0.00166 0.00212 0.00476 0.00119 0.00160 0.00096 0.00360 0.00636 0.00202 0.00103 0.01329 
   2238    2247    2256    2281    2282    2316    2333    2335    2379    2407    2409    2456    2511    2512    2542 
0.00179 0.01336 0.01353 0.01512 0.00088 0.00108 0.00342 0.00349 0.04853 0.00301 0.00091 0.01030 0.18683 0.00163 0.00849 
   2572    2591    2593    2598    2659    2668    2756    2782    2829    2845    2867    2911    2942    2973    2978 
0.00293 0.00742 0.02359 0.00175 0.05088 0.00443 0.02664 0.00090 0.00257 0.00088 0.00890 0.01106 0.00343 0.00188 0.00134 
   3015    3060    3072    3133    3142    3176    3235    3308    3310    3312    3327    3350    3429    3457    3467 
0.00110 0.00177 0.15030 0.11017 0.00464 0.00161 0.00451 0.00115 0.00101 0.00278 0.00136 0.00196 0.00253 0.62693 0.00477 
   3477    3478    3526    3565    3567    3572    3595    3605    3636    3651    3674    3692    3761    3791    3820 
0.00408 0.00295 0.02680 0.00364 0.00154 0.00951 0.00548 0.00088 0.00511 0.00957 0.00154 0.02193 0.00192 0.03384 0.00150 
   3856    3886    3893    3901    3907    3916    3937    3958    3991    3993    4049    4067    4099    4133    4136 
0.00126 0.00131 0.00520 0.00122 0.09218 0.00316 0.00092 0.00087 0.00086 0.00232 0.00104 0.00818 0.00300 0.01615 0.00338 
   4162    4173    4184    4226    4230    4254    4313    4334    4335    4365    4395    4432    4449    4451    4485 
0.01574 0.00123 0.00192 0.00416 0.00166 0.02118 0.01745 0.01160 0.00122 0.00218 0.00189 0.00792 0.00097 0.00194 0.00208 
   4510    4522    4630    4635    4641    4700 
0.00110 0.00138 0.00202 0.00216 0.00754 0.00089 

Cook Distance

To determine which leverage points are actual influential points, we apply the Cook distance.

cookdis <- cooks.distance(view_full)
cookdis[cookdis>1]
 355  664  794 1732 3072 3457 3907 
 1.2 17.6  1.1  4.2  4.4  9.9  3.4 

Thus, the data points with indices below are influential:

\[\text{Influential Point Indices}: 355, 664, 794, 1732, 3072, 3457, 3907\]

So, it might be better to remove them from our data before any modelling.

new_data <- num_feature[-c(355, 664,794,1732,3072,3457,3907), ]
new_lm <- lm(views ~ likes + dislikes + comment_count, data = new_data)
summary(new_lm)

Call:
lm(formula = views ~ likes + dislikes + comment_count, data = new_data)

Residuals:
      Min        1Q    Median        3Q       Max 
-1.17e+08 -1.82e+06 -1.49e+06 -4.30e+05  4.59e+08 

Coefficients:
               Estimate Std. Error t value Pr(>|t|)    
(Intercept)    1.59e+06   2.02e+05    7.84  5.4e-15 ***
likes          1.41e+02   2.75e+00   51.34  < 2e-16 ***
dislikes       3.67e+02   2.42e+01   15.18  < 2e-16 ***
comment_count -3.56e+02   2.03e+01  -17.53  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1.3e+07 on 4701 degrees of freedom
Multiple R-squared:  0.509, Adjusted R-squared:  0.508 
F-statistic: 1.62e+03 on 3 and 4701 DF,  p-value: <2e-16

But the influential point removal would decrease the adj-\(R^2\). It is because there are other leverage points that are not influential points and so they are not impacting on the linear model. So in evaluation they adversely impact on the adj-\(R^2\).

summary(lm(formula = views ~ likes + dislikes , data = new_data))

Call:
lm(formula = views ~ likes + dislikes, data = new_data)

Residuals:
      Min        1Q    Median        3Q       Max 
-1.43e+08 -1.87e+06 -1.60e+06 -5.20e+05  4.59e+08 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) 1.68e+06   2.09e+05    8.06  9.9e-16 ***
likes       1.08e+02   2.07e+00   52.21  < 2e-16 ***
dislikes    1.30e+02   2.07e+01    6.28  3.6e-10 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1.4e+07 on 4702 degrees of freedom
Multiple R-squared:  0.476, Adjusted R-squared:  0.476 
F-statistic: 2.14e+03 on 2 and 4702 DF,  p-value: <2e-16

Adding New Handcrafted Features

It looks like that the current predictors are not able to find a good model relating the number of views to the other variables. So, we design new features that can improve the accuracy of our model. Based on the domain knowledge, we take a careful look at the notion of trending. It sounds like the rate of changes in the views. So, we try to find such a rate. Trending videos should have high rate of changes in the number of views. Since we know the number of trending days, the initial views and the last count for the views, we can approximate this rate as follows:

\[\text{Average Daily Trending} = \frac{\text{(Last_Views_Count) - (First_Views_Count)}}{\text{The Number of Days in Trending}}\] We add this value to our data set.

Average Daily Views

options(digits=2)
new_num_feature<- us_videos %>% group_by(video_id) %>%   
  summarize(
                        first_trending_date = min(trending_date), 
                        tot_views = sum(views), tot_likes = max(likes), 
                        tot_dislikes = max(dislikes), tot_comment_count = max(comment_count), 
                        trending_days = length(video_id), 
                        avg_daily_views = round((max(views)-min(views))/length(video_id))
                        ) 
new_num_feature

View vs Average Daily Views

ggplot(new_num_feature, aes(y = tot_views, x = avg_daily_views)) + geom_point()

Correlations Among the New Variables

cor_matrix <- cor(new_num_feature[, c("tot_views", "tot_likes", "tot_dislikes", "tot_comment_count", "avg_daily_views")])
corrplot(cor_matrix, method = "number", order = "hclust")

#method = "circle", order = "hclust")

Full Extended Model vs Reduced Extended Model

Just like the previous models, if we let all variables be in the model, the there would be high VIF (~ 7.9) for the comment_counts.

car::vif(lm(tot_views ~ tot_likes + tot_dislikes + tot_comment_count + avg_daily_views, data = new_num_feature))
        tot_likes      tot_dislikes tot_comment_count   avg_daily_views 
              5.5               5.4               7.9               3.3 

So we already removed it and design a reduced models without it.

new_view_lm <- lm(tot_views ~ tot_likes + tot_dislikes +  avg_daily_views, data = new_num_feature)
summary(new_view_lm)

Call:
lm(formula = tot_views ~ tot_likes + tot_dislikes + avg_daily_views, 
    data = new_num_feature)

Residuals:
      Min        1Q    Median        3Q       Max 
-1.82e+08 -7.60e+05 -3.53e+05  2.38e+05  3.31e+08 

Coefficients:
                Estimate Std. Error t value Pr(>|t|)    
(Intercept)     3.65e+05   1.85e+05    1.97    0.049 *  
tot_likes       4.29e+01   1.90e+00   22.51  < 2e-16 ***
tot_dislikes    2.92e+01   5.79e+00    5.04  4.8e-07 ***
avg_daily_views 3.72e+01   6.27e-01   59.36  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1.2e+07 on 4708 degrees of freedom
Multiple R-squared:  0.771, Adjusted R-squared:  0.771 
F-statistic: 5.29e+03 on 3 and 4708 DF,  p-value: <2e-16

The adj-\(R^2\) is pretty high and much better than the previous models. So, we stick to this model. Moreover, this model has no high VIF.

car::vif(new_view_lm)
      tot_likes    tot_dislikes avg_daily_views 
            2.3             1.4             2.5 

Other Interesting Statistics

We would like to know which categories are the most popular categories. So we simply count them and apply a barplot for the counts.

cat_count <- us_videos %>% group_by(category_id) %>%   summarize(count = length(category_id)) 
ggplot(cat_count,aes(x = category_id, y = count,fill=category_id))+geom_bar(stat="identity")

Conclusion

We worked with an interesting data set for trending YouTube videos. This data set has several features such as the number of counts, the number of views, the number of likes and dislikes, the title of the video, etc. We focused on the continuous features and tried to find a linear model for the number of views in terms of other numeric features. We had three original models whose adj-\(R^2\) are not high. So, we started handcrafting new features. The new feature is the average daily views for each video. This new feature could highly contribute to the model. If we know this value for the first two days of trending, we can approximate the number of views after a specific period of time. Having the best set of features in a data analysis problem is the most significant part.

Appendix

The category titles are available here.

cat_title <- c('People & Blogs', 'Comedy', 'News & Politics', 'Entertainment',
       'Sports', 'Autos & Vehicles', 'Gaming', 'Film & Animation',
       'Howto & Style', 'Science & Technology', 'Shows', 'Music',
       'Travel & Events', 'Education', 'Pets & Animals',
       'Nonprofits & Activism')
length((cat_title))
[1] 16
LS0tDQp0aXRsZTogIkxpbmVhciBSZWdyZXNzaW9uIE1vZGVscyBmb3IgVHJlbmRpbmcgWW91VHViZSBWaWRlb3MiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KIyMgR2hvZHJhdG9sbGFoIEFhbGlwb3VyIChnYTU0ODFAcml0LmVkdSkNCg0KDQoNCiMjIyBEZXNjcmlwdGlvbg0KWW91VHViZSBtYWludGFpbnMgYSBsaXN0IG9mIHRoZSB0b3AgdHJlbmRpbmcgdmlkZW9zLiBUbyBkZXRlcm1pbmUgdGhlIHllYXIncyB0b3AtdHJlbmRpbmcgdmlkZW9zLA0KWW91VHViZSB1c2VzIGEgY29tYmluYXRpb24gb2YgZmFjdG9ycyBpbmNsdWRpbmcgbWVhc3VyaW5nIHVzZXJzIGludGVyYWN0aW9ucyAobnVtYmVyIG9mIHZpZXdzLA0Kc2hhcmVzLCBjb21tZW50cyBhbmQgbGlrZXMpLiBPdXIgZGF0YXNldCBmb3IgdGhpcyBwcm9qZWN0IGlzIGRlcml2ZWQgZnJvbSBbMV0uIA0KDQpPdXIgZGF0YXNldCBpcyBhIGRhaWx5IHJlY29yZCBvZiB0aGUgdG9wIHRyZW5kaW5nIFlvdVR1YmUgdmlkZW9zIGFuZCBpbmNsdWRlcyBzZXZlcmFsIG1vbnRocyAoYW5kDQpjb3VudGluZykgb2YgZGF0YSBvbiBkYWlseSB0cmVuZGluZyBZb3VUdWJlIHZpZGVvcy4gVGhlIHJlZ2lvbnMgdGhhdCB0aGlzIGRhdGFzZXQgY292ZXJzIGFyZSB0aGUgVVMsDQpHQiwgREUsIENBLCBhbmQgRlIgcmVnaW9ucyAoVVNBLCBHcmVhdCBCcml0YWluLCBHZXJtYW55LCBDYW5hZGEsIGFuZCBGcmFuY2UsIHJlc3BlY3RpdmVseSksIHdpdGggdXANCnRvIDIwMCBsaXN0ZWQgdHJlbmRpbmcgdmlkZW9zIHBlciBkYXkuIEVhY2ggcmVnaW9uJ3MgZGF0YSBpcyBpbiBhIHNlcGFyYXRlIGZpbGUuIFdlIGZvY3VzIG9uIHRoZSBmaWxlIGZvcg0KdGhlIFVuaXRlZCBTdGF0ZXMuIEthZ2dsZSBjb2xsZWN0ZWQgdGhpcyBkYXRhIHRocm91Z2ggWW91VHViZSBBUEkuIA0KDQpUaGUgZGF0YSBpbmNsdWRlcyBzZXZlcmFsIGZlYXR1cmVzIHN1Y2ggYXMgdGhlIHZpZGVvIHRpdGxlLCB0cmVuZGluZ19kYXRlLCBjaGFubmVsIHRpdGxlLCBwdWJsaXNoIHRpbWUsIHRhZ3MsIHZpZXdzLCBsaWtlcyBhbmQgZGlzbGlrZXMsIGRlc2NyaXB0aW9uLCBhbmQgY29tbWVudCBjb3VudCB3aXRoIDIzMzYzIG9ic2VydmF0aW9ucy4gVGhlIHZhcmlhYmxlcyBsaWtlcywgZGlzbGlrZXMsIGNvbW1lbnQgY291bnRzIGFyZSB0aGUgb25seSBjb250aW51b3VzIHZhcmlhYmxlcy4gVGhlcmUgZXhpc3QgdGV4dCBkYXRhIGFuZCB0aW1lLXNlcmllcyBkYXRhIGFzIHdlbGwuDQoNCiMjIyBHb2FsDQpJbiB0aGlzIHByb2plY3Qgd2UgYWltIHRvIHJlbGF0ZSB0aGUgbnVtYmVyIG9mIHZpZXdzIHRvIG90aGVyIG51bWVyaWMgdmFyaWFibGVzIHRocm91Z2ggYSBsaW5lYXINCnJlZ3Jlc3Npb24gbW9kZWwgYW5kIHBlcmZvcm0gc3RhdGlzdGljYWxseSBhbmFseXNpcyB0ZWNobmlxdWVzIHRoYXQgd2UgbGVhcm5lZCBpbiB0aGUgY291cnNlLiBUaGlzIGluY2x1ZGVzIGlkZW50aWZ5aW5nIHRoZSBmYWN0b3JzIHRoYXQgaGF2ZSBtb3N0IGluZmx1ZW5jZSBvbiB0aGUgbW9kZWwuIFdlIHN0YXJ0IHdpdGggbG9hZGluZyB0aGUgZGF0YSBhbmQgcHJlcHJvY2VzcyBpdCB0byBoYW5kbGUgbWlzc2luZyB2YWx1ZXMgZXRjLiBGaXJzdCwgd2UgaW5jbHVkZSBhbGwgdGhlIHBhY2thZ2VzIHRoZSB3ZSBuZWVkIGZvciB0aGlzIHByb2plY3QuIA0KDQojIyMgUmVxdWlyZWQgTGlicmFyaWVzDQoNCmBgYHtyfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoY29ycnBsb3QpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCg0KYGBgDQoNCkFzIHdlIHBvaW50ZWQgb3V0LCB0aGUgZGF0YSBpcyBnaXZlbiBpbiBbMV0uIEJ1dCB3ZSBkb3dubG9hZCBpdCBhbmQgc2F2ZSBpdCB0byB0aGUgZGlzay4gDQoNCg0KIyMjIExvYWRpbmcgdGhlIERhdGENCmBgYHtyfQ0KdXNfdmlkZW9zIDwtIHJlYWQuY3N2KCJVU3ZpZGVvcy5jc3YiKQ0KdXNfdmlkZW9zDQpgYGANCiMjIERhdGEgUHJlcHJvY2Vzc2luZyANCg0KV2Ugc3RhcnQgd2l0aCBzb21lIGluaXRpYWwgZGF0YSBwcm9jZXNzaW5nLiBXZSBkbyBub3QgbmVlZCBhbGwgZmVhdHVyZXMgb2YgdGhlIGRhdGEuIFRvIG9jY3VweSBsZXNzIG1lbW9yeSwgIHdlIGRyb3AgIHVubmVjZXNzYXJ5IGNvbHVtbnMuIA0KDQojIyMgRHJvcHBpbmcgVW5mYXZvcmFibGUgQ29sdW1ucw0KDQpXZSBkcm9wIHRoZSBmb2xsb3dpbmcgY29sdW1uczogInZpZGVvX2lkIiwgInRyZW5kaW5nX2RhdGUiLCAgInZpZXdzIiwgImxpa2VzIiwgImRpc2xpa2VzIiwgImNvbW1lbnRfY291bnQiLCAiY2F0ZWdvcnlfaWQiLCAicHVibGlzaF90aW1lIiwiY2hhbm5lbF90aXRsZSIsIGFuZCAidGl0bGUiLiANCg0KYGBge3J9DQpmYXZvcl9jb2xzIDwtIGMoInZpZGVvX2lkIiwgInRyZW5kaW5nX2RhdGUiLCAgInZpZXdzIiwgImxpa2VzIiwgImRpc2xpa2VzIiwgImNvbW1lbnRfY291bnQiLCANCiAgICAgICAgICAgICAgICAiY2F0ZWdvcnlfaWQiLCAicHVibGlzaF90aW1lIiwiY2hhbm5lbF90aXRsZSIsICJ0aXRsZSIpDQoNCnVzX3ZpZGVvcyA8LSB1c192aWRlb3NbICwgZmF2b3JfY29sc10gDQp1c192aWRlb3MkY2F0ZWdvcnlfaWQgPC0gYXMuZmFjdG9yKHVzX3ZpZGVvcyRjYXRlZ29yeV9pZCkNCnVzX3ZpZGVvcw0KYGBgDQoNCiMjIyBTdGFuZGFyZCBEYXRlIEZvcm1hdCBmb3IgVHJlbmRpbmdfZGF0ZSBhbmQgUHVibGlzaF90aW1lDQoNCldlIHR1cm5pbmcgdGhlIGRhdGEgdHlwZSBmb3IgdHJlbmRpbmdfZGF0ZSBhbmQgcHVibGlzaF90aW1lIGZyb20gZmFjdG9yIGludG8gYSBzdGFuZGFyZCBkYXRlIGZvcm1hdC4NCiANCg0KYGBge3J9DQp1c192aWRlb3MkdHJlbmRpbmdfZGF0ZSA8LSB5ZG0odXNfdmlkZW9zJHRyZW5kaW5nX2RhdGUpDQp1c192aWRlb3MkcHVibGlzaF90aW1lIDwtIHltZChzdWJzdHIodXNfdmlkZW9zJHB1Ymxpc2hfdGltZSxzdGFydCA9IDEsc3RvcCA9IDEwKSkNCnVzX3ZpZGVvcw0KYGBgDQoNCg0KIyMjIEFueSBNaXNzaW5nIHZhbHVlPw0KQXMgdGhlIGNvZGUgYmxvY2sgYmVsb3cgc2hvd3MsIHRoZXJlIGlzIG5vIG1pc3NpbmcgdmFsdWUgaW4gb3VyIGRhdGEuDQoNCmBgYHtyfQ0Kc3VtKGlzLm5hKHVzX3ZpZGVvcykpDQpgYGANCg0KDQoNCiMjIyBVbmRlcnN0YW5kaW5nICBPdXIgRGF0YSBCZXR0ZXIgDQoNCl9fUXVlc3Rpb246IERvIHdlIGhhdmUgZXhhY3RseSBvbmUgb2JzZXJ2YXRpb24gcGVyIHZpZGVvPyBfXw0KDQpUaGUgdmlkZW9zIGFyZSBkZXRlcm1pbmVkIGJ5IHRoZWlyIElEJ3MuIFNvLCB3ZSBjb3VudCB0aGUgbnVtYmVyIG9mIGRpZmZlcmVudCBJRCdzIA0KYXMgYSBmYWN0b3IgdmFyaWFibGUuIElmIHRoaXMgbnVtYmVyIG1hdGNocyB3aXRoIHRoZSB0b3RhbCBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zLCB0aGVuIHdlIGhhdmUgZXhhY3RseSBvbmUgaW5zdGFuY2UgcGVyIHZpZGVvLiANCg0KYGBge3J9DQpsZW5ndGgobGV2ZWxzKGFzLmZhY3Rvcih1c192aWRlb3MkdmlkZW9faWQpKSkNCmBgYA0KDQpIZW5jZSwgd2Ugb25seSBoYXZlICQ0LDcxMiQgIHZpZGVvcyBidXQgdGhlIG51bWJlciBvZiBvYnNlcnZhdGlvbnMgaXMgJDIzLDM2MiQuIFNvIHdlIGhhdmUgcmVkdW5kYW50IGluZm9ybWF0aW9uIGZvciBhIA0Kc2luZ2xlIHZpZGVvLiBGb3IgaW5zdGFuY2UsIGZvciBhIHZpZGVvIHdob3NlIElEIGlzICIya3lTNlN2U1lTRSIsIHdlIHRha2UgYSBsb29rIHRvIHNlZSB3aGF0IGluZm9ybWF0aW9uIGNhbiBiZSBjYXB0dXJlZCBmb3JtIHRoaXMgdmlkZW8uIA0KDQpgYGB7cn0NCnVzX3ZpZGVvc1t1c192aWRlb3MkdmlkZW9faWQgPT0gIjJreVM2U3ZTWVNFIixdDQpgYGANCg0KQXMgaXQgY2FuIGJlIHNlZW4gZnJvbSB0aGlzIHF1ZXJ5LCBmb3IgdGhpcyB2aWRlbyBpZCB3ZSBoYXZlICQ3JCBvYnNlcnZhdGlvbnMuIEFmdGVyIGEgY2FyZWZ1bCBsb29rIGF0IHRoZSBkYXRhLCB3ZSByZWFsaXplIHRoYXQgZWFjaCBpcyBwZXIgdHJlbmRpbmcgZGF5LiBUaGlzIHZpZGVvIGhhcyBiZWVuIGluIHRyZW5kaW5nIGZvciBzZXZlbiBkYXlzIGZyb20gTm92ZW1iZXIgMTQsIDIwMTEgdG8gTm92ZW1iZXIgMjAsIDIwMTEuIEl0IHdhcyBwdWJsaXNoZWQgaW4gTm92ZW1iZXIgMTMuIFRodXMsIGVhY2ggdmlkZW8gaGFzIGFwcGVhcmVkIGluIHRoZSBvYnNlcnZhdGlvbnMgYXMgbWFueSBkYXlzIGFzIGl0IGhhcyBiZWVuIHRyZW5kaW5nLiANCg0KV2UgY2FuIHVzZSB0aGUgZ3JvdXBfYnkgbWV0aG9kIG9mICJkcGx5ZXIiIGxpYnJhcnkgb2YgUiB0byBncm91cCB0aGUgZGF0YSBvYnNlcnZhdGlvbnMgYnkgdGhlaXIgdmlkZW9faWQuDQogDQoNCmBgYHtyfQ0KYXJyYW5nZSh1c192aWRlb3MsIHZpZGVvX2lkLCBkZXNjKHRyZW5kaW5nX2RhdGUpKQ0KYGBgDQoNCiMjIyBIb3cgaXMgdGhlIERhdGEgQ29sbGVjdGVkPw0KDQpBcmUgdGhlIGNvdW50cyBmb3Igdmlld3MsIGxpa2VzLCBkaXNsaWtlcyBhbmQgY29tbWVudF9jb3VudCBjb21wdXRlZCBjdW11bGF0aXZlbHkgb3IgYXJlIHRoZXkgcmVzZW50aW5nIHRoZSB2YWx1ZXMgZm9yIGVhY2ggZGF5LiBVbmZvcnR1bmF0ZWx5LCBJIGRpZCBub3QgZmluZCBhbnkgaW5mb3JtYXRpb24gaW4gdGhlIGRhdGEgc291cmNlIHJlZ2FyZGluZyB0aGlzIGlzc3VlLiBJIGFzc3VtZSB0aGF0IHRoZXkgYXJlIGNhbGN1bGF0ZWQgY3VtdWxhdGl2ZWx5IGFzIHBlciB3YXRjaCBhIHZpZGVvIGluIHRoZSBZb3VUdWJlLCB0aGUgbnVtYmVyIG9mIHZpZXdzIGluY3JlYXNlcyBieSBvbmUuICANCg0KDQojIyMgUmVtb3ZpbmcgSGlzdG9yeSBmb3IgVHJlbmQNCg0KV2UgZXh0cmFjdCBhY3R1YWwgbnVtZXJpYyB2YWx1ZXMgYnkgYWdncmVnYXRpbmcgb3ZlciB0aGUgaGlzdG9yeSBmb3IgZWFjaCB2aWRlbyBzbyB0aGF0IHdlIGhhdmUgZXhhY3RseSBvbmUgZGF0YSBpbnN0YW5jZSBmb3IgZWFjaCB2aWRlby4gV2UgY2FsbCB0aGUgbmV3IGRhdGFzZXQgYnkgIm51bV9mZWF0dXJlIi4NCg0KDQpgYGB7cn0NCm51bV9mZWF0dXJlPC0gdXNfdmlkZW9zICU+JSBncm91cF9ieSh2aWRlb19pZCkgJT4lICAgDQogIHN1bW1hcml6ZSgNCiAgICAgICAgICAgICAgICAgICAgICAgIHZpZXdzID0gc3VtKHZpZXdzKSwgbGlrZXMgPSBtYXgobGlrZXMpLCANCiAgICAgICAgICAgICAgICAgICAgICAgIGRpc2xpa2VzID0gbWF4KGRpc2xpa2VzKSwgY29tbWVudF9jb3VudCA9IG1heChjb21tZW50X2NvdW50KQ0KICAgICAgICAgICAgICAgICAgICAgICAgKSANCm51bV9mZWF0dXJlDQpgYGANCg0KIyMjIEJveHBsb3QgZm9yIFZpZXdzIEJhc2VkIG9uIHRoZSBDYXRlZ29yeSBJRA0KDQpXZSBwbG90IHRoZSBtZWFuLCBtZWRpYW4gYW5kIHZhcmlhbmNlIG9mIHZpZXdzIHBlciBjYXRlZ29yeV9pZC4gVW5mb3J0dW5hdGVseSwgdGhlcmUgYXJlIGV4dHJlbWUgb3V0bGllcnMgYW5kIHRoZXkgZG8gbm90IGFsbG93IHNlZSB0aGUgZGV0YWlscyB3ZWxsIGluIHRoZSBwbG90Lg0KIA0KDQpgYGB7cn0NCnVzX3ZpZGVvcyAlPiUgZ3JvdXBfYnkoY2F0ZWdvcnlfaWQpICU+JSAgIA0KICBzdW1tYXJpemUobWVhbl92aWV3cyA9IG1lYW4odmlld3MpLCBzZF92aWV3cyA9IHNkKHZpZXdzKSkgDQoNCmBgYA0KDQoNCmBgYHtyfQ0KY2F0ZWdfZGF0YSA8LSB1c192aWRlb3MgJT4lIGdyb3VwX2J5KHZpZGVvX2lkLCBjYXRlZ29yeV9pZCkgJT4lICAgDQogIHN1bW1hcml6ZSgNCiAgICAgICAgICAgICAgICAgICAgICAgIHZpZXdzID0gc3VtKHZpZXdzKSwgbGlrZXMgPSBtYXgobGlrZXMpLCANCiAgICAgICAgICAgICAgICAgICAgICAgIGRpc2xpa2VzID0gbWF4KGRpc2xpa2VzKSwgY29tbWVudF9jb3VudCA9IG1heChjb21tZW50X2NvdW50KQ0KICAgICAgICAgICAgICAgICAgICAgICAgKSANCnAgPC0gZ2dwbG90KGNhdGVnX2RhdGEsIGFlcyh4PWNhdGVnb3J5X2lkLCB5PXZpZXdzLCBjb2xvciA9IGNhdGVnb3J5X2lkKSApKyBnZW9tX2JveHBsb3Qob3V0bGllci5jb2xvdXI9ImJsYWNrIiwgb3V0bGllci5zaGFwZT0xNiwNCiAgICAgICAgICAgICBvdXRsaWVyLnNpemU9Miwgbm90Y2g9RkFMU0UpICsgY29vcmRfZmxpcCgpDQpwDQpgYGANCg0KDQojIyMgTW9kZWxsaW5nIFRoZSBEYXRhIA0KV2Ugc3RhcnQgd2l0aCB0aGUgZnVsbCBtb2RlbCwgd2hpY2ggaW5jbHVkZSBhbGwgdmFyaWFibGVzLiANCg0KYGBge3J9DQp2aWV3X2Z1bGwgPC0gbG0odmlld3MgfiBsaWtlcyArIGRpc2xpa2VzICsgY29tbWVudF9jb3VudCwgZGF0YSA9IG51bV9mZWF0dXJlKQ0Kc3VtbWFyeSh2aWV3X2Z1bGwpDQpgYGANCg0KVGhlIHAtdmFsdWUgb2YgdGhlIEYtc3RhdGlzdGljIGlzIGxlc3MgdGhhbiAkMi4yKjEwXnstMTZ9JCB3aGljaCBtZWFucyB0aGF0IHRoZSByZWdyZXNzaW9uIGlzIHNpZ25pZmljYW50LCBpLmUuIHRoZSBjb2VmZmljaWVudHMgb2YgYXQgbGVhc3Qgb25lIHRoZSBwcmVkaWN0b3JzICJsaWtlcyIsICJkaXNsaWtlcyIsICJjb21tZW50X2NvdW50IiBpcyBub24temVyby4gIA0KDQpUaGUgcmVncmVzc2lvbiBtb2RlbCBkZXRlcm1pbmVkIGJ5IHRoZSBsZWFzdCBzcXVhcmUgbWV0aG9kIG9uIHRoaXMgZGF0YXNldCBpcw0KDQoNCiQke1xiZiB2aWV3c30gPSAxMjU1MDAwICsgMTYxLjUgKiAoe1xiZiBsaWtlc30pICsgMzc4LjkgKiAoe1xiZiBkaXNsaWtlc30pIC0gNDUwLjQgKiAoe1xiZiBjb21tZW50XF9jb3VudH0pJCQgDQpUaGUgYWRqLSRSXjIkIGlzICQwLjY0JCB3aGljaCBpcyBub3QgdGhhdCBoaWdoLiBUaGlzIHZhbHVlIGNvdWxkIGJlIGR1ZSB0byBtdWx0aWNvbGxpbmVhcml0eSBhbW9uZyB0aGUgcHJlZGljdG9ycy4gDQpXZSBjaGVjayBmb3IgY29sbGluZWFyaXR5IGFtb25nIHRoZSBwcmVkaWN0b3JzLiANCg0KIyMjIENvcnJlbGF0aW9ucyBBbW9uZyB0aGUgTmV3IFZhcmlhYmxlcw0KDQpgYGB7cn0NCmNvcl9tYXRyaXggPC0gY29yKG51bV9mZWF0dXJlWywgYygidmlld3MiLCAibGlrZXMiLCAiZGlzbGlrZXMiLCAiY29tbWVudF9jb3VudCIpXSkNCmNvcnJwbG90KGNvcl9tYXRyaXgsIG1ldGhvZCA9ICJudW1iZXIiKSAjb3JkZXIgPSAiaGNsdXN0Ig0KYGBgDQoNCiMjIyBNdWx0aS1Db2xsaW5lYXJpdHkNCg0KQXMgdGhlIGNvcnJlbGF0aW9uIG1hdHJpeCBzaG93cywgdGhlcmUgaXMgaGlnaCBjb2xsaW5lYXJpdHkgYmV0d2VlbiBzb21lIHZhcmlhYmxlcy4gRm9yIGluc3RhbmNlLCBkaXNsaWtlcyBhbmQgY29tbWVudCBjb3VudCBoYXZlIGhpZ2ggY29ycmVsYXRpb25zLiBUaGlzIG1ha2VzIHNlbnNlLCBiZWNhdXNlIHVzdWFsbHkgd2hlbiBwZW9wbGUgYXJlIGFuZ3J5IGFib3V0IGEgcHJvZHVjdCwgdGhleSB3b3VsZCB3cml0ZSBhIHJldmlldy4gU2ltaWxhcmx5LCB2aWV3cyBhbmQgbGlrZXMgaGF2ZSBoaWdoIGNvcnJlbGF0aW9uIGFzIHdlbGwuIFNvIGxpa2VzIHNlZW1zIHRvIGJlIGEgZ29vZCBwcmVkaWN0b3IgZm9yIHRoZSBudW1iZXIgb2Ygdmlld3MuIE1vcmVvdmVyLCBsaWtlcyBhbmQgdGhlIGNvbW1lbnQgY291bnRzIGhhdmUgcmVtYXJrYWJsZSBjb3JyZWxhdGlvbi4gVGh1cywgaWYgb25lIG9mIHRoZSBwcmVkaWN0b3JzIGxpa2VzIG9yIGNvbW1lbnQgY291bnQgaXMgIGF2YWlsYWJsZSBpbiB0aGUgbW9kZWwsIHRoZSBvdGhlciBvbmUgbWlnaHQgYmUgY29uc2lkZXJlZCBmb3IgZXhjbHVzaW9uIGZyb20gdGhlIG1vZGVsLiBXZSB3aWxsIGV4YW1pbmUgdGhlc2UgZGVwZW5kZW5jaWVzIGxhdGVyIHdoZW4gd2UgYXJlIG1vZGVsbGluZy4gDQoNCiMjIyBWYXJpYW5jZSBJbmZsYXRpb24gRmFjdG9ycyAtIFZJRiBTY29yZSANCg0KV2UgY2FsY3VsYXRlIHRoZSBWYXJpYW5jZSBJbmZsYXRpb24gRmFjdG9yIChWSUYpIGFtb25nIHRoZSBwcmVkaWN0b3IgdmFyaWFibGVzLg0KDQpgYGB7cn0NCmNhcjo6dmlmKHZpZXdfZnVsbCkNCmBgYA0KDQpUaGUgVklGIGZvciAiY29tbWVudF9jb3VudCIgaXMgYmVnaWllciB0aGF0ICQ1JC4gU2luY2UgJDUgXGxlcSBWSUYgXGxlcSAxMCQgYXJlIGNvbnNpZGVyZWQgc2lnbmlmaWNhbnQsIHRoZSBwcmVkaWN0b3IgImNvbW1lbnRfY291bnQiIHdvdWxkIHBvb3JseSBlc3RpbWF0ZSB0aGUgcmVncmVzc2lvbiBjb2VmZmljaWVudC4gIA0KDQojIyMgQ29uZGl0aW9uIE51bWJlcg0KYGBge3J9DQprYXBwYShudW1fZmVhdHVyZVssYygibGlrZXMiLCAiZGlzbGlrZXMiLCAiY29tbWVudF9jb3VudCIpXSkNCmBgYA0KDQojIyBEcm9wcGluZyB0aGUgY29tbWVudF9jb3VudCBmcm9tIHRoZSBGdWxsIE1vZGVsDQoNCldlIHN0YXJ0IHJlZHVjaW5nIG91ciBtb2RlbCBieSBkcm9wcGluZyB0aGUgcHJlZGljdG9yIHdob3NlIFZJRiBpcyBoaWdoLiBUaHVzLCB3ZSBkcm9wIHRoZSAiY29tbWVudF9jb3VudCIgZnJvbSBvdXIgZnVsbCBtb2RlbC4gVGhlbiwgd2UgZXZhbHVhdGUgdGhlIHJlZHVjZWQgbW9kZWwgZm9yIGl0cyBWSUYuDQoNCg0KYGBge3J9DQp2aWV3X2xpa2VfZGlzbGlrZSA8LSBsbSh2aWV3cyB+IGxpa2VzICsgZGlzbGlrZXMsIGRhdGEgPSBudW1fZmVhdHVyZSkNCmNhcjo6dmlmKHZpZXdfbGlrZV9kaXNsaWtlKQ0KYGBgDQoNCiMjIyBWSUYgRm9yIHRoZSBOZXcgUmVkdWNlZCBNb2RlbA0KQXMgaXQgaXMgY2xlYXIgZnJvbSB0aGUgbmV3IFZJRidzIGZvciB0aGUgbmV3IG1vZGVsLCBlYWNoIG9mIG5ldyAibGlrZXMiIGFuZCAiZGlzbGlrZXMiIGhhdmUgYSBnb29kIFZJRi4gU28sIHdlIHByZWZlciB0aGUgcmVkdWNlZCBtb2RlbCBvdmVyIHRoZSBmdWxsIG1vZGVsLiAgDQoNCiMjIyBIb3cgQWJvdXQgdGhlIGFkai0kUl4yJCBmb3IgdGhlIHJlZHVjZWQgTW9kZWw/IA0KVGhvdWdoLCB3ZSBkbyBub3Qgc2VlIGFueSBzaWduaWZpY2FudCBWSUYsIHdlIHNob3VsZCBjb21wYXJlIHRoZSBwZXJmb3JtYW5jZSBvZiB0aGUgcmVkdWNlZCBtb2RlbCB3aXRoIHRoZSBmdWxsIG1vZGVsIGluIHRlcm1zIG9mIHRoZSBhZGotJFJeMiQuDQoNCg0KYGBge3J9DQpzdW1tYXJ5KHZpZXdfbGlrZV9kaXNsaWtlKQ0KYGBgDQpUaGUgcmVncmVzc2lvbiBtb2RlbCBpbmR1Y2VkIGJ5IHRoZXNlIHByZWRpY3RvcnMgaXMgDQoNCiQkdmlld3MgPSAxMjcyMDAwICsgMTE5LjkgKiAobGlrZXMpICsgMTMyLjEqIChkaXNsaWtlcykkJA0KVGhlIGFkai0kUl4yJCBmb3IgdGhlIG5ldyBtb2RlbCBpcyAkMC41OSQgYnV0IGZvciB0aGUgZnVsbCBtb2RlbCBpcyAkMC42NCQuIFNvLCB3ZSBhcmUgbWlzc2luZyBzb21lIGFtb3VudCBvZiBhY2N1cmFjeS4gSWYgdGhlcmUgd2FzIG5vIGxvc3Mgb24gdGhlIGFjY3VyYWN5LCB0aGVuIHRoZSByZWR1Y2VkIG1vZGVsIGlzIHByZWZlcnJlZCBvdmVyIHRoZSBmdWxsIG1vZGVsLiBCdXQgd2l0aCB0aGlzIGxvc3MsIHdlIG1heSBuZWVkIHRvIGNoZWNrIG90aGVyIG1vZGVsIGNhbmRpZGF0ZXMgYnkgcmVkdWNpbmcgdGhlIG1vZGVsIGV2ZW4gZnVydGhlci4gDQoNCg0KIyMjIFJlZ3Jlc3Npb24gUmVsYXRpbmcgVmlld3MgdG8gTGlrZXMNCg0KDQpgYGB7cn0NCnZpZXdfbGlrZSA8LSBsbSh2aWV3cyB+IGxpa2VzLCBkYXRhID0gbnVtX2ZlYXR1cmUpDQpzdW1tYXJ5KHZpZXdfbGlrZSkNCmBgYA0KDQpUaHVzLCB0aGUgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgYmV0d2VlbiAidmlld3MiIGFuZCAibGlrZXMiIGlzIGRldGVybWluZWQgYnkgJHZpZXdzID0gMTAxNjAwMCArIDEzNSoobGlrZXMpJC4gQXMgdGhlIHAtdmFsdWUgZm9yIHRoZSBGLXN0YXRpc3RpYyBzaG93LCB0aGUgcmVncmVzc2lvbiBpcyBfX3NpZ25pZmljYW50X18uIFRoZSBhZGotJFJeMiQgZm9yIHRoaXMgdGhpcmQgbW9kZWwgaXMgJDAuNTckIHdoaWNoIGlzIGxvd2VyIHRoYW4gdGhlIGFkai0kUl4yJCBmb3IgdGhlIHNlY29uZCBtb2RlbCBhbmQgdGhlIGZpcnN0IG1vZGVsLiANCg0KIyMjIENvbmZpZGVuY2UgYW5kIFByZWRpY3Rpb24gSW50ZXJ2YWxzDQpXZSB0YWtlIGEgbG9vayBhdCB0aGUgY29uZmlkZW5jZSBpbnRlcnZhbHMgYW5kIHByZWRpY3Rpb24gaW50ZXJ2YWxzLg0KDQoNCmBgYHtyfQ0KdGVtcF9wcmVkaWMgPC0gcHJlZGljdCh2aWV3X2xpa2UsIGludGVydmFsPSJwcmVkaWN0aW9uIikNCm5ld19kZiA8LSBjYmluZChudW1fZmVhdHVyZSwgdGVtcF9wcmVkaWMpDQpnZ3Bsb3QobmV3X2RmLCBhZXMobGlrZXMsIHZpZXdzKSkrDQpnZW9tX3BvaW50KCkgKw0KZ2VvbV9saW5lKGFlcyh5PWx3ciksIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpKw0KZ2VvbV9saW5lKGFlcyh5PXVwciksIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpKw0KZ2VvbV9zbW9vdGgobWV0aG9kPWxtLCBzZT1UUlVFKQ0KYGBgDQoNCiMjIFdoYXQgTW9kZWwgdG8gU2VsZWN0PyANCg0KV2Ugc3VtbWFyaXplIG91ciBtb2RlbHMgaW4gdGhlIHRhYmxlIGJlbG93OiANCg0KIVtUaHJlZSBMaW5lYXIgUmVncmVzc2lvbiBNb2RlbHNdKFJlc3VsdHNfdGFibGUucG5nKSANCg0KQXMgaXQgaXMgY2xlYXIgZnJvbSB0aGUgdGFibGUsIGJ5IHJlZHVjaW5nIHRoZSBtb2RlbHMgd2UgbWlzcyBzb21lIGFtb3VudCBvZiBhY2N1cmFjeS4gRnJvbSB0aGUgZmlyc3QgbW9kZWwgdG8gdGhlIHNlY29uZCBtb2RlbCwgd2UgbG9zZSBhYm91dCAkNFwlJCBvZiBhY2N1cmFjeS4gQnV0IHRoZSB0cmFkZW9mZiBpcyB0aGF0IHdlIHdvbid0IGhhdmUgdGhlIGNvbGxpbmVhcml0eSBpbiB0aGlzIG1vZGVsLiANClRoZSBsYXN0IG1vZGVsIGhhcyBvbmx5IG9uZSBwcmVkaWN0b3IgYW5kIGhhcyBsZXNzIGFjY3VyYWN5IGluIHRlcm1zIG9mIGFkai0kUl4yJC4gU28gdGhlIGJlc3QgbW9kZWwgY291bGQgYmUgdGhlIHNlY29uZCBtb2RlbC4gDQoNCg0KDQoNCiMjIFNjYXR0ZXIgUGxvdCBmb3IgRGF0YQ0KDQoNCiMjIyBWaWV3cyB2cyBMaWtlcw0KDQpgYGB7cn0NCmdncGxvdChudW1fZmVhdHVyZSwgYWVzKHggPSBsaWtlcywgeSA9IHZpZXdzKSkgKyBnZW9tX3BvaW50KCkNCmBgYA0KDQoNCiMjIyBWaWV3cyB2cyBEaXNsaWtlcw0KYGBge3J9DQpnZ3Bsb3QobnVtX2ZlYXR1cmUsIGFlcyh4ID0gZGlzbGlrZXMsIHkgPSB2aWV3cykpICsgZ2VvbV9wb2ludCgpDQpgYGANCg0KDQoNCiMjIyBWaWV3cyB2cyBDb21tZW50IENvdW50DQpgYGB7cn0NCmdncGxvdChudW1fZmVhdHVyZSwgYWVzKHkgPSB2aWV3cywgeCA9IGNvbW1lbnRfY291bnQpKSArIGdlb21fcG9pbnQoKQ0KYGBgDQoNCiMjIyBCdWlsZGluZyBMaW5lYXIgTW9kZWxzIFJlbGF0aW5nIE90aGVyIFByZWRpY3RvcnMgDQpBcyB3ZSByZWFsaXplZCB0aHJvdWdoIHRoZSBjb3JyZWxhdGlvbiBtYXRyaXgsIHRoZXJlIHNlZW1zIHRvIGJlIGxpbmVhciByZWxhdGlvbiBiZXR3ZWVuIHZpZXdzIGFuZCBsaWtlcyBhbmQgYW5vdGhlciBsaW5lYXIgcmVsYXRpb24gYmV0d2VlbiBkaXNsaWtlcyBhbmQgY29tbWVudF9jb3VudC4gV2UgaW52ZXN0aWdhdGUgZWFjaCBvZiB0aGVzZSBzaW5nbGUgdmFyaWFibGUgbGluZWFyIHJlbGF0aW9ucyB0aHJvdWdoIGFwcGx5aW5nIGxlYXN0IHNxdWFyZSBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbHMgYW5kIGNoZWNrIGZvciB0aGUgX19zaWduaWZpY2FuY2Ugb2YgcmVncmVzc2lvbl9fLiANCg0KDQoNCg0KIyMjIFJlZ3Jlc3Npb24gUmVsYXRpbmcgQ29tbWVudCBDb3VudCB0byBEaXNsaWtlcw0KDQpgYGB7cn0NCmRpc2xpa2VfY29tbWVudCA8LSBsbShjb21tZW50X2NvdW50IH4gZGlzbGlrZXMsIGRhdGEgPSBudW1fZmVhdHVyZSkNCnN1bW1hcnkoZGlzbGlrZV9jb21tZW50KQ0KYGBgDQoNClRodXMsIG91ciBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbCBiZXR3ZWVuICJjb21tZW50X2NvdW50IiBhbmQgImRpc2xpa2VzIiBpcyBkZXRlcm1pbmVkIGJ5ICRjb21tZW50XF9jb3VudCA9IDMxOTkgKyAwLjcxMyhkaXNsaWtlcykkLiBBcyB0aGUgcC12YWx1ZSBmb3IgdGhlIEYtc3RhdGlzdGljIGlzIHRvbyBsb3csIHRoZSByZWdyZXNzaW9uIGlzIF9fc2lnbmlmaWNhbnRfXy4NCg0KYGBge3J9DQp0ZW1wX3ByZWRpYyA8LSBwcmVkaWN0KGRpc2xpa2VfY29tbWVudCwgaW50ZXJ2YWw9InByZWRpY3Rpb24iKQ0KbmV3X2RmIDwtIGNiaW5kKG51bV9mZWF0dXJlLCB0ZW1wX3ByZWRpYykNCmdncGxvdChuZXdfZGYsIGFlcyh4ID0gZGlzbGlrZXMsIHkgPSBjb21tZW50X2NvdW50KSkrDQpnZW9tX3BvaW50KCkgKw0KZ2VvbV9saW5lKGFlcyh5PWx3ciksIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpKw0KZ2VvbV9saW5lKGFlcyh5PXVwciksIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpKw0KZ2VvbV9zbW9vdGgobWV0aG9kPWxtLCBzZT1UUlVFKQ0KYGBgDQpgYGB7cn0NCmFub3ZhKGRpc2xpa2VfY29tbWVudCkNCmBgYA0KDQoNCg0KIyMjIENhdGNoaW5nIEluZmx1ZW50aWFsIERhdGEgUG9pbnRzIA0KDQpUaGUgcGxvdHMgYWJvdmUgc2hvdyB0aGF0IHRoZXJlIGFyZSBzb21lIGV4dHJlbWUgb3V0bGluZXJzIGluIG91ciBkYXRhc2V0LiBUaGUgaW5mbHVlbnRpYWwgZGF0YSBwb2ludHMgYWR2ZXJzZWx5IGltcGFjdCBvbiBvdXIgcmVncmVzc2lvbiBtb2RlbHMgYW5kIGNhbiBmYXZvciBzb21lIHJlZ3Jlc3Npb24gY29lZmZpY2llbnRzLiBUbyBhZGRyZXNzIHRoaXMgcHJvYmxlbSwgd2Ugc2hvdWxkIGZpbmQgc3VjaCBwb2ludC4gV2Ugc3RhcnQgd2l0aCB0aGUgbGV2ZXJhZ2UgcG9pbnRzOg0KDQojIyMjIExldmVyYWdlIFBvaW50cw0KVGhlIGZvbGxvd2luZyBpbmRpY2VzIHNob3cgdGhhdCB0aGUgY29ycmVzcG9uZGluZyBkYXRhIHBvaW50cyBhcmUgbGV2ZXJhZ2UgcG9pbnRzLg0KDQpgYGB7cn0NCnAgPSAyDQpuID0gNDcxMg0KaW5mbGMgPSBpbmZsdWVuY2Uodmlld19mdWxsKQ0KaW5mbGMkaGF0W2luZmxjJGhhdD4yKnAvbl0NCmBgYA0KDQojIyMgQ29vayBEaXN0YW5jZQ0KVG8gZGV0ZXJtaW5lIHdoaWNoIGxldmVyYWdlIHBvaW50cyBhcmUgYWN0dWFsIGluZmx1ZW50aWFsIHBvaW50cywgd2UgYXBwbHkgdGhlIENvb2sgZGlzdGFuY2UuIA0KDQpgYGB7cn0NCmNvb2tkaXMgPC0gY29va3MuZGlzdGFuY2Uodmlld19mdWxsKQ0KY29va2Rpc1tjb29rZGlzPjFdDQpgYGANCg0KVGh1cywgdGhlIGRhdGEgcG9pbnRzIHdpdGggaW5kaWNlcyBiZWxvdyBhcmUgaW5mbHVlbnRpYWw6DQoNClxbXHRleHR7SW5mbHVlbnRpYWwgIFBvaW50IEluZGljZXN9OiAzNTUsIDY2NCwgNzk0LCAxNzMyLCAzMDcyLCAzNDU3LCAzOTA3XF0NCg0KU28sIGl0IG1pZ2h0IGJlIGJldHRlciB0byByZW1vdmUgdGhlbSBmcm9tIG91ciBkYXRhIGJlZm9yZSBhbnkgbW9kZWxsaW5nLg0KDQpgYGB7cn0NCm5ld19kYXRhIDwtIG51bV9mZWF0dXJlWy1jKDM1NSwgNjY0LDc5NCwxNzMyLDMwNzIsMzQ1NywzOTA3KSwgXQ0KbmV3X2xtIDwtIGxtKHZpZXdzIH4gbGlrZXMgKyBkaXNsaWtlcyArIGNvbW1lbnRfY291bnQsIGRhdGEgPSBuZXdfZGF0YSkNCnN1bW1hcnkobmV3X2xtKQ0KYGBgDQpCdXQgdGhlIGluZmx1ZW50aWFsIHBvaW50IHJlbW92YWwgd291bGQgZGVjcmVhc2UgdGhlIGFkai0kUl4yJC4gSXQgaXMgYmVjYXVzZSB0aGVyZSBhcmUgb3RoZXIgbGV2ZXJhZ2UgcG9pbnRzIHRoYXQgYXJlIG5vdCANCmluZmx1ZW50aWFsIHBvaW50cyBhbmQgc28gdGhleSBhcmUgbm90IGltcGFjdGluZyBvbiB0aGUgbGluZWFyIG1vZGVsLiBTbyBpbiBldmFsdWF0aW9uIHRoZXkgYWR2ZXJzZWx5IGltcGFjdCBvbiB0aGUgYWRqLSRSXjIkLg0KDQpgYGB7cn0NCnN1bW1hcnkobG0oZm9ybXVsYSA9IHZpZXdzIH4gbGlrZXMgKyBkaXNsaWtlcyAsIGRhdGEgPSBuZXdfZGF0YSkpDQpgYGANCg0KDQoNCg0KDQoNCg0KDQoNCg0KIyMgQWRkaW5nIE5ldyBIYW5kY3JhZnRlZCBGZWF0dXJlcw0KSXQgbG9va3MgbGlrZSB0aGF0IHRoZSBjdXJyZW50IHByZWRpY3RvcnMgYXJlIG5vdCBhYmxlIHRvIGZpbmQgYSBnb29kIG1vZGVsIHJlbGF0aW5nIHRoZSBudW1iZXIgb2Ygdmlld3MgdG8gdGhlIG90aGVyIHZhcmlhYmxlcy4gU28sIHdlIGRlc2lnbiBuZXcgZmVhdHVyZXMgdGhhdCBjYW4gaW1wcm92ZSB0aGUgYWNjdXJhY3kgb2Ygb3VyIG1vZGVsLiBCYXNlZCBvbiB0aGUgZG9tYWluIGtub3dsZWRnZSwgd2UgdGFrZSBhIGNhcmVmdWwgbG9vayBhdCB0aGUgbm90aW9uIG9mIHRyZW5kaW5nLiBJdCBzb3VuZHMgbGlrZSB0aGUgcmF0ZSBvZiBjaGFuZ2VzIGluIHRoZSB2aWV3cy4gU28sIHdlIHRyeSB0byBmaW5kIHN1Y2ggYSByYXRlLiBUcmVuZGluZyB2aWRlb3Mgc2hvdWxkIGhhdmUgaGlnaCByYXRlIG9mIGNoYW5nZXMgaW4gdGhlIG51bWJlciBvZiB2aWV3cy4gU2luY2Ugd2Uga25vdyB0aGUgbnVtYmVyIG9mIHRyZW5kaW5nIGRheXMsIHRoZSBpbml0aWFsIHZpZXdzIGFuZCB0aGUgbGFzdCBjb3VudCBmb3IgdGhlIHZpZXdzLCB3ZSBjYW4gYXBwcm94aW1hdGUgdGhpcyByYXRlIGFzIGZvbGxvd3M6DQoNCiQkXHRleHR7QXZlcmFnZSBEYWlseSBUcmVuZGluZ30gPSBcZnJhY3tcdGV4dHsoTGFzdF9WaWV3c19Db3VudCkgLSAoRmlyc3RfVmlld3NfQ291bnQpfX17XHRleHR7VGhlIE51bWJlciBvZiBEYXlzIGluIFRyZW5kaW5nfX0kJA0KV2UgYWRkIHRoaXMgdmFsdWUgdG8gb3VyIGRhdGEgc2V0LiANCg0KIyMjIEF2ZXJhZ2UgRGFpbHkgVmlld3MgDQoNCmBgYHtyfQ0Kb3B0aW9ucyhkaWdpdHM9MikNCm5ld19udW1fZmVhdHVyZTwtIHVzX3ZpZGVvcyAlPiUgZ3JvdXBfYnkodmlkZW9faWQpICU+JSAgIA0KICBzdW1tYXJpemUoDQogICAgICAgICAgICAgICAgICAgICAgICBmaXJzdF90cmVuZGluZ19kYXRlID0gbWluKHRyZW5kaW5nX2RhdGUpLCANCiAgICAgICAgICAgICAgICAgICAgICAgIHRvdF92aWV3cyA9IHN1bSh2aWV3cyksIHRvdF9saWtlcyA9IG1heChsaWtlcyksIA0KICAgICAgICAgICAgICAgICAgICAgICAgdG90X2Rpc2xpa2VzID0gbWF4KGRpc2xpa2VzKSwgdG90X2NvbW1lbnRfY291bnQgPSBtYXgoY29tbWVudF9jb3VudCksIA0KICAgICAgICAgICAgICAgICAgICAgICAgdHJlbmRpbmdfZGF5cyA9IGxlbmd0aCh2aWRlb19pZCksIA0KICAgICAgICAgICAgICAgICAgICAgICAgYXZnX2RhaWx5X3ZpZXdzID0gcm91bmQoKG1heCh2aWV3cyktbWluKHZpZXdzKSkvbGVuZ3RoKHZpZGVvX2lkKSkNCiAgICAgICAgICAgICAgICAgICAgICAgICkgDQpuZXdfbnVtX2ZlYXR1cmUNCmBgYA0KDQojIyMgVmlldyB2cyBBdmVyYWdlIERhaWx5IFZpZXdzDQpgYGB7cn0NCmdncGxvdChuZXdfbnVtX2ZlYXR1cmUsIGFlcyh5ID0gdG90X3ZpZXdzLCB4ID0gYXZnX2RhaWx5X3ZpZXdzKSkgKyBnZW9tX3BvaW50KCkNCmBgYA0KDQoNCg0KIyMjIENvcnJlbGF0aW9ucyBBbW9uZyB0aGUgTmV3IFZhcmlhYmxlcw0KDQpgYGB7cn0NCmNvcl9tYXRyaXggPC0gY29yKG5ld19udW1fZmVhdHVyZVssIGMoInRvdF92aWV3cyIsICJ0b3RfbGlrZXMiLCAidG90X2Rpc2xpa2VzIiwgInRvdF9jb21tZW50X2NvdW50IiwgImF2Z19kYWlseV92aWV3cyIpXSkNCmNvcnJwbG90KGNvcl9tYXRyaXgsIG1ldGhvZCA9ICJudW1iZXIiLCBvcmRlciA9ICJoY2x1c3QiKQ0KI21ldGhvZCA9ICJjaXJjbGUiLCBvcmRlciA9ICJoY2x1c3QiKQ0KYGBgDQoNCiMjIyBGdWxsIEV4dGVuZGVkIE1vZGVsIHZzIFJlZHVjZWQgRXh0ZW5kZWQgTW9kZWwNCkp1c3QgbGlrZSB0aGUgcHJldmlvdXMgbW9kZWxzLCBpZiB3ZSBsZXQgYWxsIHZhcmlhYmxlcyBiZSBpbiB0aGUgbW9kZWwsIHRoZSB0aGVyZSB3b3VsZCBiZSBoaWdoIFZJRiAofiA3LjkpIGZvciB0aGUgY29tbWVudF9jb3VudHMuDQoNCg0KYGBge3J9DQpjYXI6OnZpZihsbSh0b3Rfdmlld3MgfiB0b3RfbGlrZXMgKyB0b3RfZGlzbGlrZXMgKyB0b3RfY29tbWVudF9jb3VudCArIGF2Z19kYWlseV92aWV3cywgZGF0YSA9IG5ld19udW1fZmVhdHVyZSkpDQpgYGANCg0KDQpTbyB3ZSBhbHJlYWR5IHJlbW92ZWQgaXQgYW5kIGRlc2lnbiBhIHJlZHVjZWQgbW9kZWxzIHdpdGhvdXQgaXQuDQoNCmBgYHtyfQ0KbmV3X3ZpZXdfbG0gPC0gbG0odG90X3ZpZXdzIH4gdG90X2xpa2VzICsgdG90X2Rpc2xpa2VzICsgIGF2Z19kYWlseV92aWV3cywgZGF0YSA9IG5ld19udW1fZmVhdHVyZSkNCnN1bW1hcnkobmV3X3ZpZXdfbG0pDQpgYGANCg0KVGhlIGFkai0kUl4yJCBpcyBwcmV0dHkgaGlnaCBhbmQgbXVjaCBiZXR0ZXIgdGhhbiB0aGUgcHJldmlvdXMgbW9kZWxzLiBTbywgd2Ugc3RpY2sgdG8gdGhpcyBtb2RlbC4gTW9yZW92ZXIsIHRoaXMgbW9kZWwgaGFzIG5vIGhpZ2ggVklGLg0KYGBge3J9DQpjYXI6OnZpZihuZXdfdmlld19sbSkNCmBgYA0KDQoNCiMjIyBPdGhlciBJbnRlcmVzdGluZyBTdGF0aXN0aWNzDQoNCldlIHdvdWxkIGxpa2UgdG8ga25vdyB3aGljaCBjYXRlZ29yaWVzIGFyZSB0aGUgbW9zdCBwb3B1bGFyIGNhdGVnb3JpZXMuIFNvIHdlIHNpbXBseSBjb3VudCB0aGVtIGFuZCBhcHBseSBhIA0KYmFycGxvdCBmb3IgdGhlIGNvdW50cy4NCg0KDQpgYGB7cn0NCmNhdF9jb3VudCA8LSB1c192aWRlb3MgJT4lIGdyb3VwX2J5KGNhdGVnb3J5X2lkKSAlPiUgICBzdW1tYXJpemUoY291bnQgPSBsZW5ndGgoY2F0ZWdvcnlfaWQpKSANCmdncGxvdChjYXRfY291bnQsYWVzKHggPSBjYXRlZ29yeV9pZCwgeSA9IGNvdW50LGZpbGw9Y2F0ZWdvcnlfaWQpKStnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpDQpgYGANCg0KIyMjIEhvdyBNYW55IERheXMgaXMgTmVlZGVkIGZvciBhIFZpZGVvIHRvIEJlIFRyZW5kaW5nPw0KDQpZb3UgbWlnaHQgYmUgaW50ZXJlc3RlZCBkaW4gdGhlIGhvdyBtYW55IGRheXMgaW4gYWR2YW5jZSB5b3Ugc2hvdWxkIHVwbG9hZCBhIHZpZGVvIHRvIG1ha2UgaXQgYSBjYW5kaWRhdGUgZm9yIHRyZW5kaW5nIGluIGEgcGFydGljdWxhciBkYXkuIFdlIGFuc3dlciB0aGlzIHF1ZXN0aW9uIGJlbG93Og0KDQpgYGB7cn0NCnVzX3ZpZGVvcyRkYXlzX2FmdGVyX3B1YiA8LSB1c192aWRlb3MkdHJlbmRpbmdfZGF0ZS11c192aWRlb3MkcHVibGlzaF90aW1lDQojdXNfdmlkZW9zW3VzX3ZpZGVvcyRkYXlzX2FmdGVyX3B1YjwzMCxdDQoNCmdncGxvdCh1c192aWRlb3NbdXNfdmlkZW9zJGRheXNfYWZ0ZXJfcHViPDMwLF0sYWVzKGFzLmZhY3RvcihkYXlzX2FmdGVyX3B1YiksZmlsbD1hcy5mYWN0b3IoZGF5c19hZnRlcl9wdWIpKSkrZ2VvbV9iYXIoKStndWlkZXMoZmlsbD0ibm9uZSIpK2xhYnModGl0bGU9IiBUaGUgTnVtYmVyIG9mIERheXMgQWZ0ZXIgUHVibGlzaCBEYXkiLHN1YnRpdGxlPSJEYXlzIikreGxhYihOVUxMKSt5bGFiKE5VTEwpDQoNCmBgYA0KDQojIyMgQ29uY2x1c2lvbg0KDQpXZSB3b3JrZWQgd2l0aCBhbiBpbnRlcmVzdGluZyBkYXRhIHNldCBmb3IgdHJlbmRpbmcgWW91VHViZSB2aWRlb3MuIFRoaXMgZGF0YSBzZXQgaGFzIHNldmVyYWwgZmVhdHVyZXMgc3VjaCBhcyB0aGUgbnVtYmVyIG9mIGNvdW50cywgdGhlIG51bWJlciBvZiB2aWV3cywgdGhlIG51bWJlciBvZiBsaWtlcyBhbmQgZGlzbGlrZXMsIHRoZSB0aXRsZSBvZiB0aGUgdmlkZW8sIGV0Yy4gV2UgZm9jdXNlZCBvbiB0aGUgY29udGludW91cyBmZWF0dXJlcyBhbmQgdHJpZWQgdG8gZmluZCBhIGxpbmVhciBtb2RlbCBmb3IgdGhlIG51bWJlciBvZiB2aWV3cyBpbiB0ZXJtcyBvZiBvdGhlciBudW1lcmljIGZlYXR1cmVzLiBXZSBoYWQgdGhyZWUgb3JpZ2luYWwgbW9kZWxzIHdob3NlIGFkai0kUl4yJCBhcmUgbm90IGhpZ2guIFNvLCB3ZSBzdGFydGVkIGhhbmRjcmFmdGluZyBuZXcgZmVhdHVyZXMuIFRoZSBuZXcgZmVhdHVyZSBpcyB0aGUgYXZlcmFnZSBkYWlseSB2aWV3cyBmb3IgZWFjaCB2aWRlby4gVGhpcyBuZXcgZmVhdHVyZSBjb3VsZCBoaWdobHkgY29udHJpYnV0ZSB0byB0aGUgbW9kZWwuIElmIHdlIGtub3cgdGhpcyB2YWx1ZSBmb3IgdGhlIGZpcnN0IHR3byBkYXlzIG9mIHRyZW5kaW5nLCB3ZSBjYW4gYXBwcm94aW1hdGUgdGhlIG51bWJlciBvZiB2aWV3cyBhZnRlciBhIHNwZWNpZmljIHBlcmlvZCBvZiB0aW1lLiBIYXZpbmcgdGhlIGJlc3Qgc2V0IG9mIGZlYXR1cmVzIGluIGEgZGF0YSBhbmFseXNpcyBwcm9ibGVtIGlzIHRoZSBtb3N0IHNpZ25pZmljYW50IHBhcnQuICANCg0KDQojIyMgUmVmZXJlbmNlcw0KDQpbMV0gaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9kYXRhc25hZWsveW91dHViZS1uZXcuIA0KDQoNCg0KDQojIyMgQXBwZW5kaXgNClRoZSBjYXRlZ29yeSB0aXRsZXMgYXJlIGF2YWlsYWJsZSBoZXJlLg0KDQpgYGB7cn0NCmNhdF90aXRsZSA8LSBjKCdQZW9wbGUgJiBCbG9ncycsICdDb21lZHknLCAnTmV3cyAmIFBvbGl0aWNzJywgJ0VudGVydGFpbm1lbnQnLA0KICAgICAgICdTcG9ydHMnLCAnQXV0b3MgJiBWZWhpY2xlcycsICdHYW1pbmcnLCAnRmlsbSAmIEFuaW1hdGlvbicsDQogICAgICAgJ0hvd3RvICYgU3R5bGUnLCAnU2NpZW5jZSAmIFRlY2hub2xvZ3knLCAnU2hvd3MnLCAnTXVzaWMnLA0KICAgICAgICdUcmF2ZWwgJiBFdmVudHMnLCAnRWR1Y2F0aW9uJywgJ1BldHMgJiBBbmltYWxzJywNCiAgICAgICAnTm9ucHJvZml0cyAmIEFjdGl2aXNtJykNCmxlbmd0aCgoY2F0X3RpdGxlKSkNCg0KYGBgDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQo=